/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *            Copyright (C) 2018 Institute of Computing Technology, CAS
 *               Author : Han Shukai (email : hanshukai@ict.ac.cn)
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *         The kernel's entry, where most of the initialization work is done.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * *
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  * * * * * * * * * * */

#include <common.h>
#include <os/irq.h>
#include <os/mm.h>
#include <os/sched.h>

#include <sbi.h>
#include <stdio.h>
#include <os/time.h>
#include <os/syscall.h>
#include <os/ioremap.h>
#include <os/futex.h>
#include <os/queue.h>
#include <pgtable.h>
#include <os/ipc.h>
#include <csr.h>
#include <os/elf.h>

#include <assert.h>
#include <screen.h>
#include <plic.h>
#include <emacps/xemacps_example.h>
#include <net.h>

//int volatile context_offset=288;
extern void ret_from_exception();

extern void __global_pointer$();

queue_t ready_queue;
queue_t sleep_queue;


extern pid_t process_id;

static void init_memory(){
    init_kmem();
    sbi_console_putchar('5');
    freerange(FREEMEM,0xffffffc05df00000);
    return;
}

static void init_pcb()
{
    
    int i=1;

    queue_init(&ready_queue);
    queue_init(&sleep_queue);
    pcb[i].preempt_count=1;
    pcb[i].pid= process_id++;
    init_pgtable(&(pcb[i].pgtable),&(pcb[i].kernel_sp));
    pcb[i].user_sp = USER_STACK_ADDR;
    walkaddr((uint64_t)USER_STACK_ADDR-NORMAL_PAGE_SIZE,(PTE *)pcb[i].pgtable);
    char shell[]="shell";
    char **binary=shell;
    int length=0;
    uint64_t entrypoint;
    if(get_elf_file(shell,binary,&length)){
        entrypoint = load_elf(*binary,length,pcb[i].pgtable,walkaddr);
    }else
        printk("Load shell failed !!\n\r");
    for(int j=0;j<32;j++)
        pcb[i].kernel_context.regs[j]=0;
    pcb[i].kernel_context.regs[1]=(uintptr_t)&ret_from_exception;
    pcb[i].kernel_context.regs[2]=pcb[i].kernel_sp;
    pcb[i].kernel_context.regs[3]=__global_pointer$;
    pcb[i].kernel_context.sstatus=0x40022;
    queue_init(&pcb[i].wait_queue);
    pcb[i].kernel_context.sepc=(uintptr_t)entrypoint;
    for(int j=0;j<32;j++)
        pcb[i].user_context.regs[j]=0;
    pcb[i].user_context.regs[1]=(uintptr_t)&ret_from_exception;
    pcb[i].user_context.regs[2]=pcb[i].user_sp;
    pcb[i].user_context.regs[3]=__global_pointer$;
    pcb[i].user_context.sstatus=0x40022;
    pcb[i].user_context.sepc=(uintptr_t)entrypoint;
    pcb[i].status = TASK_READY;
    pcb[i].type = USER_PROCESS;
    queue_push(&ready_queue,(void *)&pcb[i]);
    pcb[i].cursor_x=0;
    pcb[i].cursor_y=0;
    current_running = &pcb[0];
    current_running->pgtable=(uint64_t)pa2kva(PGDIR_PA);
    for (int i = 2; i < NUM_MAX_TASK; ++i)
    {
        pcb[i].status=TASK_EXITED;
    }
    
}

static void init_syscall(void)
{
    // initialize system call table.
    int i;
    syscall[SYSCALL_SLEEP] = (long int (*)())&do_sleep;
    syscall[SYSCALL_BLOCK] = (long int (*)())&do_block;
    syscall[SYSCALL_UNBLOCK] = (long int (*)())&do_unblock;
    syscall[SYSCALL_YIELD] = (long int (*)())&do_scheduler;
    syscall[SYSCALL_WRITE] = (long int (*)())&screen_write;
    syscall[SYSCALL_CURSOR] = (long int (*)())&screen_move_cursor;

    syscall[SYSCALL_REFLUSH] = (long int (*)())&screen_reflush;
    syscall[SYSCALL_GET_TIMEBASE] = (long int (*)())&get_time_base;
    syscall[SYSCALL_GET_TICK] = (long int (*)())&get_ticks;
    syscall[SYSCALL_SPAWN] = (long int (*)())&do_spawn;
    syscall[SYSCALL_EXIT] = (long int (*)())&do_exit;
    syscall[SYSCALL_EXEC] = (long int (*)())&do_exec;
    syscall[SYSCALL_SHOW_EXEC] = (long int (*)())&do_show_exec;
    syscall[SYSCALL_KILL] = (long int (*)())&do_kill;
    syscall[SYSCALL_WAITPID] = (long int (*)())&do_waitpid;
    syscall[SYSCALL_PS] = (long int (*)())&do_process_show;
    syscall[SYSCALL_GETPID] = (long int (*)())&do_getpid;
    syscall[SYSCALL_GET_CHAR] = (long int (*)())&sbi_console_getchar;
    syscall[SYSCALL_SCREEN_CLEAR]=(long int (*)())&do_clear;

    syscall[SYSCALL_BINSEMGET]=(long int (*)())&do_binsemget;
    syscall[SYSCALL_BINSEMOP]=(long int (*)())&do_binsemop;

    syscall[SYSCALL_BARRIER_INIT]=(long int (*)())&do_barrier_init;
    syscall[SYSCALL_BARRIER_WAIT]=(long int (*)())&do_barrier_wait;
    syscall[SYSCALL_BARRIER_DESTROY]=(long int (*)())&do_barrier_destroy;

    syscall[SYSCALL_CONDITION_INIT]=(long int (*)())&do_cond_init;
    syscall[SYSCALL_CONDITION_WAIT]=(long int (*)())&do_cond_wait;
    syscall[SYSCALL_CONDITION_DESTROY]=(long int (*)())&do_cond_destroy;
    syscall[SYSCALL_CONDITION_SIGNAL]=(long int (*)())&do_cond_signal;
    syscall[SYSCALL_CONDITION_BROADCAST]=(long int (*)())&do_cond_broadcast;

    syscall[SYSCALL_MBOX_OPEN]=(long int (*)())&do_mbox_open;
    syscall[SYSCALL_MBOX_CLOSE]=(long int (*)())&do_mbox_close;
    syscall[SYSCALL_MBOX_SEND]=(long int (*)())&do_mbox_send;
    syscall[SYSCALL_MBOX_RECV]=(long int (*)())&do_mbox_recv;

    syscall[SYSCALL_SHMPGET]=(long int (*)())&do_shmpageget;
    syscall[SYSCALL_SHMPDT]=(long int (*)())&do_shmpagedt;

    syscall[SYSCALL_NET_RECV]=(long int (*)())&do_net_recv;
    syscall[SYSCALL_NET_SEND]=(long int (*)())&do_net_send;
    syscall[SYSCALL_NET_IRQ_MODE]=(long int (*)())&do_net_irq_mode;

}
void boot_first_core(uint64_t mhartid)
{
    init_memory();
    // init Process Control Block (-_-!)

    // setup timebase
    // fdt_print(_dtb);
    // get_prop_u32(_dtb, "/cpus/cpu/timebase-frequency", &time_base);
    current_running=&pcb[0];
    current_running->pgtable=pa2kva(PGDIR_PA);
    PTE *root_pgtable=pa2kva(PGDIR_PA);
    root_pgtable[1]=0;
    //setup_exception();
    time_base = sbi_read_fdt(TIMEBASE);
    uint32_t slcr_bade_addr = 0, ethernet_addr = 0;

    // get_prop_u32(_dtb, "/soc/slcr/reg", &slcr_bade_addr);
    slcr_bade_addr = sbi_read_fdt(SLCR_BADE_ADDR);
    printk("[slcr] phy: 0x%x\n\r", slcr_bade_addr);

    // get_prop_u32(_dtb, "/soc/ethernet/reg", &ethernet_addr);
    ethernet_addr = sbi_read_fdt(ETHERNET_ADDR);
    printk("[ethernet] phy: 0x%x\n\r", ethernet_addr);

    uint32_t plic_addr = 0;
    // get_prop_u32(_dtb, "/soc/interrupt-controller/reg", &plic_addr);
    plic_addr = sbi_read_fdt(PLIC_ADDR);
    printk("[plic] plic: 0x%x\n\r", plic_addr);

    uint32_t nr_irqs = sbi_read_fdt(NR_IRQS);
    // get_prop_u32(_dtb, "/soc/interrupt-controller/riscv,ndev", &nr_irqs);
    printk("[plic] nr_irqs: 0x%x\n\r", nr_irqs);

    XPS_SYS_CTRL_BASEADDR =
        (uintptr_t)ioremap((uint64_t)slcr_bade_addr, NORMAL_PAGE_SIZE);
    xemacps_config.BaseAddress =
        (uintptr_t)ioremap((uint64_t)ethernet_addr, NORMAL_PAGE_SIZE);
    uintptr_t _plic_addr =
        (uintptr_t)ioremap((uint64_t)plic_addr, 0x4000*NORMAL_PAGE_SIZE);
    // XPS_SYS_CTRL_BASEADDR = slcr_bade_addr;
    // xemacps_config.BaseAddress = ethernet_addr;
    xemacps_config.DeviceId        = 0;
    xemacps_config.IsCacheCoherent = 0;

    printk(
        "[slcr_bade_addr] phy:%x virt:%lx\n\r", slcr_bade_addr,
        XPS_SYS_CTRL_BASEADDR);
    printk(
        "[ethernet_addr] phy:%x virt:%lx\n\r", ethernet_addr,
        xemacps_config.BaseAddress);
    printk("[plic_addr] phy:%x virt:%lx\n\r", plic_addr, _plic_addr);
    plic_init(_plic_addr, nr_irqs);
    printk("1\n\r");
    long status = EmacPsInit(&EmacPsInstance);
    printk("1\n\r");
    if (status != XST_SUCCESS) {
        printk("Error: initialize ethernet driver failed!\n\r");
        assert(0);
    
    }
    init_pcb();
    printk("> [INIT] PCB initialization succeeded.\n\r");
    // init futex mechanism
    init_system_futex();



    // init system call table (0_0)
    init_syscall();
    printk("> [INIT] System call initialized successfully.\n\r");

    // enable_interrupt();
    net_poll_mode = 1;
    // xemacps_example_main();

    // init screen (QAQ)
    init_screen();
    printk("> [INIT] SCREEN initialization succeeded.\n\r");
    // screen_clear(0, SCREEN_HEIGHT - 1);
    // init interrupt (^_^)
    init_exception();
    //printk("> [INIT] Interrupt processing initialization succeeded.\n\r");
}
// jump from bootloader.
// The beginning of everything >_< ~~~~~~~~~~~~~~
int main()
{
    boot_first_core(0);
    // init Process Control Block (-_-!)
    /*init_memory();
    init_pcb();
    printk("> [INIT] PCB initialization succeeded.\n\r");

    // read CPU frequency
    time_base = sbi_read_fdt(TIMEBASE);
    
    // init futex mechanism
    init_system_futex();
    printk("> [INIT] Futex initialization succeeded.\n\r");
    init_syscall();
    printk("> [INIT] System call initialized successfully.\n\r");
    init_screen();
    printk("> [INIT] SCREEN initialization succeeded.\n\r");
    init_exception();
    //printk("> [INIT] Interrupt processing initialization succeeded.\n\r");
    // TODO:
    // Setup timer interrupt and enable all interrupt
    */
    while (1) {
        // (QAQQQQQQQQQQQ)
        // If you do non-preemptive scheduling, you need to use it
        // to surrender control do_scheduler();
        // enable_interrupt();
        // __asm__ __volatile__("wfi\n\r":::);
        do_scheduler();
    };
    return 0;
}
